package com.liustar.common.util;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Created by Leo on 2017/7/4.
 */
@Component
public class RedisUtils {
    private final static Logger logger = LogManager.getLogger(RedisUtils.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private RedisTemplate<String, Object> redisTemplateTransaction;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /** ============================ */
    /**  通用操作 - Begin            */
    /** ============================ */
    /**
     * 保存指定key值的value
     *
     * @param key
     * @param value
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public void set(final String key, Object value, long timeout) {
        redisTemplate.opsForValue().set(key, value);
        if (timeout > 0) {
            redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public void set(final String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value);
        if (timeout > 0) {
            redisTemplate.expire(key, timeout, unit);
        }
    }

    /**
     * 批量保存指定key值的value
     *
     * @param keyValue
     */
    public void mset(Map keyValue) {
        redisTemplate.opsForValue().multiSet(keyValue);
    }

    /**
     * 得到指定key值的value
     *
     * @param key
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 批量得到指定key值的value
     *
     * @param keys
     */
    public List mget(List keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }

    /**  通用操作 - End ---------------------------------------------------------*/
    /** ============================== */
    /**  String（字符串）操作 - Begin  */
    /** ============================== */
    /**
     * 保存指定key值的value
     *
     * @param key
     * @param value
     */
    public void setString(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public void setString(final String key, String value, long timeout) {
        stringRedisTemplate.opsForValue().set(key, value);
        if (timeout > 0) {
            stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public void setString(final String key, String value, long timeout, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, value);
        if (timeout > 0) {
            stringRedisTemplate.expire(key, timeout, unit);
        }
    }

    /**
     * 批量保存指定key值的value
     *
     * @param keyValue
     */
    public void msetString(Map<String, String> keyValue) {
        stringRedisTemplate.opsForValue().multiSet(keyValue);
    }

    /**
     * 得到指定key值的value
     *
     * @param key
     */
    public String getString(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 批量得到指定key值的value
     *
     * @param keys
     * @return
     */
    public List mgetString(List keys) {
        return stringRedisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 如果 key 已经存在并且是一个字符串， APPEND 命令将 value 追加到 key 原来的值的末尾。
     * <p>
     * 如果 key 不存在， APPEND 就简单地将给定 key 设为 value ，就像执行 SET key value 一样。
     *
     * @param key
     * @param value
     * @return
     */
    public Integer append(String key, String value) {
        return redisTemplate.opsForValue().append(key, value);
    }

    public void bitcount(String key) {
    }

    public void bitop(String key) {
    }

    public void bitfield(String key) {
    }

//    public Long decr(String key) {
//        return redisTemplate.opsForValue().increment(key, -1);
//    }

    public Long decrby(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, 0 - delta);
    }

//    public <T> T get(String key) {
//        return (T) redisTemplate.opsForValue().get(key);
//    }

    public void getbit(String key) {
    }

    public void getrange(String key) {

    }

//    public <T> T getset(String key, T value) {
//        return (T) redisTemplate.opsForValue().getAndSet(key, value);
//    }

//    public Long incr(String key) {
//        return redisTemplate.opsForValue().increment(key,1);
//    }

//    public Long incrBy(String key, long delta) {
//        return redisTemplate.opsForValue().increment(key,delta);
//    }

    public Double incrByFloat(String key, double delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

//    public <T> List<T> mget(List<String> keys) {
//        return (List<T>) redisTemplate.opsForValue().multiGet(keys);
//    }

//    public <T> void mset(Map<String, T> keyValues) {
//        redisTemplate.opsForValue().multiSet(keyValues);
//    }

    public <T> Boolean msetnx(Map<String, T> keyValues) {
        return redisTemplate.opsForValue().multiSetIfAbsent(keyValues);
    }

    public void psetex(String key) {
    }

//    public <T> void set(String key, T value) {
//        redisTemplate.opsForValue().set(key, value);
//    }

    public Boolean setbit(String key, long offset, boolean value) {
        return redisTemplate.opsForValue().setBit(key, offset, value);
    }

    /**
     * 将值 value 关联到 key ，并将 key 的生存时间设为指定的时间和时间单位。
     * <p>
     * 如果 key 已经存在， SETEX 命令将覆写旧值。
     * <p>
     * 这个命令类似于以下两个命令：
     * <p>
     * SET key value
     * EXPIRE key seconds  # 设置生存时间
     * 不同之处是， SETEX 是一个原子性(atomic)操作，关联值和设置生存时间两个动作会在同一时间内完成，该命令在 Redis 用作缓存时，非常实用。
     *
     * @param key
     */
    public <T> void setex(String key, T value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 将 key 的值设为 value ，当且仅当 key 不存在。
     * <p>
     * 若给定的 key 已经存在，则 SETNX 不做任何动作。
     * <p>
     * SETNX 是『SET if Not eXists』(如果不存在，则 SET)的简写。
     *
     * @param key
     */
    public <T> Boolean setNX(String key, T value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 用 value 参数覆写(overwrite)给定 key 所储存的字符串值，从偏移量 offset 开始。
     * <p>
     * 不存在的 key 当作空白字符串处理。
     * <p>
     * SETRANGE 命令会确保字符串足够长以便将 value 设置在指定的偏移量上，如果给定 key 原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长，但你设置的 offset 是 10 )，
     * 那么原字符和偏移量之间的空白将用零字节(zerobytes, "\x00" )来填充。
     * <p>
     * 注意你能使用的最大偏移量是 2^29-1(536870911) ，因为 Redis 字符串的大小被限制在 512 兆(megabytes)以内。如果你需要使用比这更大的空间，你可以使用多个 key 。
     *
     * @param key
     * @param value
     * @param offset
     */
    public <T> void setRange(String key, T value, long offset) {
        redisTemplate.opsForValue().set(key, value, offset);
    }

    /**
     * 返回 key 所储存的字符串值的长度。
     * <p>
     * 当 key 储存的不是字符串值时，返回一个错误。
     *
     * @param key
     * @return
     */
    public Long strLen(String key) {
        return redisTemplate.opsForValue().size(key);
    }

    /**  ----- String（字符串）操作 - End -------------------------------------------*/
    /** ============================ */
    /**  Hash（哈希表）操作 - Begin  */
    /** ============================ */
    /**
     * 将哈希表 key 中的域 field 的值设为 value 。
     * 如果 key 不存在，一个新的哈希表被创建并进行 HSET 操作。
     * 如果域 field 已经存在于哈希表中，旧值将被覆盖。
     *
     * @param key
     * @param hashKey
     * @param value
     */
    public void hset(String key, Object hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    /**
     * 返回哈希表 key 中给定域 field 的值
     *
     * @param key
     * @param hashKey
     * @return
     */
    public Object hget(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    /**
     * 将哈希表 key 中的域 field 的值设置为 value ，当且仅当域 field 不存在。
     *
     * @param key
     * @param hashKey
     * @param value
     */
    public void hsetnx(String key, Object hashKey, Object value) {
        redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
    }

    /**
     * 返回哈希表 key 中，所有的域和值
     *
     * @param key
     * @return Map
     */
    public Map<Object, Object> hgetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 向key对应的map中添加缓存对象
     *
     * @param key
     * @param map
     */
    public <T> void hmset(String key, Map<String, T> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * <p>返回哈希表 key 中，一个或多个给定域的值</p>
     * <p>Get values for given {@code hashKeys} from hash at {@code key}</p>
     *
     * @param key      键
     * @param hashKeys 域
     * @return
     */
    public List<Object> hmget(String key, List<Object> hashKeys) {
        return redisTemplate.opsForHash().multiGet(key, hashKeys);
    }

    /**
     * 删除哈希表 key 中的一个或多个指定域，不存在的域将被忽略
     *
     * @param key
     * @param hashKeys
     */
    public void hdel(String key, Object... hashKeys) {
        redisTemplate.opsForHash().delete(key, hashKeys);
    }

    /**
     * 查看哈希表 key 中，给定域 field 是否存在
     *
     * @param key
     * @param hashKey
     */
    public boolean hexists(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    /**
     * 返回哈希表 key 中的所有域
     *
     * @param key
     */
    public Set<Object> hkeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    /**
     * 为哈希表 key 中的域 field 的值加上增量 increment 。
     * 增量也可以为负数，相当于对给定域进行减法操作。
     * 如果 key 不存在，一个新的哈希表被创建并执行 HINCRBY 命令。
     * 如果域 field 不存在，那么在执行命令前，域的值被初始化为 0 。
     *
     * @param key
     * @param hashKey
     * @param delta
     */
    public void hincrBy(String key, Object hashKey, long delta) {
        redisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    /**
     * 为哈希表 key 中的域 field 加上浮点数增量 increment 。
     * 如果哈希表中没有域 field ，那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ，然后再执行加法操作。
     * 如果键 key 不存在，那么 HINCRBYFLOAT 会先创建一个哈希表，再创建域 field ，最后再执行加法操作。
     * <p>
     * 当以下任意一个条件发生时，返回一个错误：
     * <ul>
     * <li>域 field 的值不是字符串类型(因为 redis 中的数字和浮点数都以字符串的形式保存，所以它们都属于字符串类型）</li>
     * <li>域 field 当前的值或给定的增量 increment 不能解释(parse)为双精度浮点数(double precision floating point number)</li>
     * </ul>
     *
     * @param key
     * @param hashKey
     * @param delta
     */
    public void hincrByFloat(String key, Object hashKey, double delta) {
        redisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    /**
     * 返回哈希表 key 中域的数量
     *
     * @param key
     * @return
     */
    public Long hlen(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 返回哈希表 key 中所有域的值。
     *
     * @param key
     * @return
     */
    public List<Object> hvals(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    /**
     * 用于迭代哈希键中的键值对
     *
     * @param key
     * @param options
     */
    public Cursor<Map.Entry<Object, Object>> hscan(String key, ScanOptions options) {
        return redisTemplate.opsForHash().scan(key, options);
    }

    /**
     * 返回哈希表 key 中， 与给定域 field 相关联的值的字符串长度（string length）。
     * 如果给定的键或者域不存在， 那么命令返回 0 。
     *
     * @return
     */
    public int hstrlen(String key, String hashKey) {
        int len = 0;
        if (hasKey(key) && hexists(key, hashKey)) {
            Object value = redisTemplate.opsForHash().get(key, hashKey);
            len = String.valueOf(value).length();
        }
        return len;
    }

    /**  Hash（哈希表）操作 - End --------------------------------------------------*/
    /** ========================== */
    /**  Set（集合）操作 - Begin   */
    /** ========================== */
    /**
     * 将一个或多个 member 元素加入到集合 key 当中，已经存在于集合的 member 元素将被忽略。
     * 假如 key 不存在，则创建一个只包含 member 元素作成员的集合。
     * 当 key 不是集合类型时，返回一个错误。
     *
     * @param key
     * @param value
     * @return
     */
    public Long sadd(String key, Object... value) {
        return redisTemplate.opsForSet().add(key, value);
    }

    /**
     * 返回集合 key 的基数(集合中元素的数量)。
     *
     * @param key
     * @return
     */
    public Long scard(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 返回一个集合的全部成员，该集合是所有给定集合之间的差集。
     * <p>
     * 不存在的 key 被视为空集。
     *
     * @param key
     * @param otherKey
     * @return
     */
    public Set<Object> sdiff(String key, String otherKey) {
        return redisTemplate.opsForSet().difference(key, otherKey);
    }

    /**
     * 返回一个集合的全部成员，该集合是所有给定集合之间的差集。
     * <p>
     * 不存在的 key 被视为空集。
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<Object> sdiff(String key, List<String> otherKeys) {
        return redisTemplate.opsForSet().difference(key, otherKeys);
    }

    /**
     * 这个命令的作用和 SDIFF 类似，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * <p>
     * 如果 destination 集合已经存在，则将其覆盖。
     * <p>
     * destination 可以是 key 本身。
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sdiffStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().differenceAndStore(key, otherKey, destKey);
    }

    /**
     * 这个命令的作用和 SDIFF 类似，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * <p>
     * 如果 destination 集合已经存在，则将其覆盖。
     * <p>
     * destination 可以是 key 本身。
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sdiffStore(String key, List<String> otherKeys, String destKey) {
        return redisTemplate.opsForSet().differenceAndStore(key, otherKeys, destKey);
    }

    /**
     * 返回一个集合的全部成员，该集合是所有给定集合的交集。
     * <p>
     * 不存在的 key 被视为空集。
     * <p>
     * 当给定集合当中有一个空集时，结果也为空集(根据集合运算定律)。
     *
     * @param key
     * @param otherKey
     * @return
     */
    public Set<Object> sinter(String key, String otherKey) {
        return redisTemplate.opsForSet().intersect(key, otherKey);
    }

    /**
     * 返回一个集合的全部成员，该集合是所有给定集合的交集。
     * <p>
     * 不存在的 key 被视为空集。
     * <p>
     * 当给定集合当中有一个空集时，结果也为空集(根据集合运算定律)。
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<Object> sinter(String key, List<String> otherKeys) {
        return redisTemplate.opsForSet().intersect(key, otherKeys);
    }

    /**
     * 这个命令类似于 SINTER 命令，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * <p>
     * 如果 destination 集合已经存在，则将其覆盖。
     * <p>
     * destination 可以是 key 本身。
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sinterStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().intersectAndStore(key, otherKey, destKey);
    }

    /**
     * 这个命令类似于 SINTER 命令，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * 如果 destination 集合已经存在，则将其覆盖。
     * destination 可以是 key 本身。
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sinterStore(String key, List<String> otherKeys, String destKey) {
        return redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey);
    }

    /**
     * 判断 member 元素是否集合 key 的成员。
     *
     * @param key
     * @param object
     * @return
     */
    public boolean sismember(String key, Object object) {
        return redisTemplate.opsForSet().isMember(key, object);
    }

    /**
     * 返回集合 key 中的所有成员。
     * 不存在的 key 被视为空集合。
     *
     * @param key
     * @return
     */
    public Set<Object> smembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 将 member 元素从 source 集合移动到 destination 集合。
     * <p>
     * SMOVE 是原子性操作。
     * <p>
     * 如果 source 集合不存在或不包含指定的 member 元素，则 SMOVE 命令不执行任何操作，仅返回 0 。否则， member 元素从 source 集合中被移除，并添加到 destination 集合中去。
     * <p>
     * 当 destination 集合已经包含 member 元素时， SMOVE 命令只是简单地将 source 集合中的 member 元素删除。
     * <p>
     * 当 source 或 destination 不是集合类型时，返回一个错误。
     *
     * @param key
     * @param value
     * @param destKey
     * @return
     */
    public boolean smove(String key, Object value, String destKey) {
        return redisTemplate.opsForSet().move(key, value, destKey);
    }

    /**
     * 返回并移除集合中的一个随机元素。
     *
     * @param key
     * @return
     */
    public Object spop(String key) {
        return redisTemplate.opsForSet().pop(key);
    }

    /**
     * 返回集合中的一个随机元素。
     *
     * @param key
     * @return
     */
    public Object srandMember(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 返回集合中的给定数量个随机元素。
     * <p>
     * 如果 count 为正数，且小于集合基数，那么命令返回一个包含 count 个元素的数组，数组中的元素各不相同。如果 count 大于等于集合基数，那么返回整个集合。
     * <p>
     * 如果 count 为负数，那么命令返回一个数组，数组中的元素可能会重复出现多次，而数组的长度为 count 的绝对值。
     * <p>
     *
     * @param key
     * @param count
     * @return
     */
    public List<Object> srandMember(String key, long count) {
        return redisTemplate.opsForSet().randomMembers(key, count);
    }

    /**
     * 移除集合 key 中的一个或多个 member 元素，不存在的 member 元素会被忽略。
     * 当 key 不是集合类型，返回一个错误。
     *
     * @param key
     * @param value
     * @return
     */
    public Long srem(String key, Object... value) {
        return redisTemplate.opsForSet().remove(key, value);
    }

    /**
     * 返回一个集合的全部成员，该集合是所有给定集合的并集。
     * 不存在的 key 被视为空集。
     *
     * @param key
     * @param otherKey
     * @return
     */
    public Set<Object> sunion(String key, String otherKey) {
        return redisTemplate.opsForSet().union(key, otherKey);
    }

    /**
     * 返回一个集合的全部成员，该集合是所有给定集合的并集。
     * 不存在的 key 被视为空集。
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<Object> sunion(String key, List<String> otherKeys) {
        return redisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * 这个命令类似于 SUNION 命令，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * 如果 destination 已经存在，则将其覆盖。
     * destination 可以是 key 本身。
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sunionStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
    }

    /**
     * 这个命令类似于 SUNION 命令，但它将结果保存到 destination 集合，而不是简单地返回结果集。
     * 如果 destination 已经存在，则将其覆盖。
     * destination 可以是 key 本身。
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sunionStore(String key, List<String> otherKeys, String destKey) {
        return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 命令用于迭代集合键中的元素。
     *
     * @param key
     * @param options
     * @return
     */
    public Cursor<Object> sscan(String key, ScanOptions options) {
        return redisTemplate.opsForSet().scan(key, options);
    }

    /**  Set（集合）操作 - End ----------------------------------------------------*/
    /** ========================= */
    /**  List（列表）操作 - Begin */
    /** ========================= */
    /**
     *
     */
    public void blpop() {
    }

    public void brpop() {
    }

    public void brpoplpush() {
    }

    /**
     * 返回列表 key 中，下标为 index 的元素。
     * <p>
     * 下标(index)参数 start 和 stop 都以 0 为底，也就是说，以 0 表示列表的第一个元素，以 1 表示列表的第二个元素，以此类推。
     * <p>
     * 你也可以使用负数下标，以 -1 表示列表的最后一个元素， -2 表示列表的倒数第二个元素，以此类推。
     * <p>
     * 如果 key 不是列表类型，返回一个错误。
     *
     * @param key
     * @param index
     * @return
     */
    public Object lindex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    /**
     * 将值 value 插入到列表 key 当中，位于值 pivot 之前或之后。
     * <p>
     * 当 pivot 不存在于列表 key 时，不执行任何操作。
     * <p>
     * 当 key 不存在时， key 被视为空列表，不执行任何操作。
     * <p>
     * 如果 key 不是列表类型，返回一个错误。
     *
     * @param key
     * @param index
     * @param value
     */
    public void linsert(String key, long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 将值 value 插入到列表 key 的表头，当且仅当 key 存在并且是一个列表。
     * <p>
     * 和 LPUSH 命令相反，当 key 不存在时， LPUSHX 命令什么也不做。
     *
     * @param key
     * @param value
     */
    public Long lpushx(String key, Object value) {
        return redisTemplate.opsForList().leftPushIfPresent(key, value);
    }

    /**
     * 根据参数 count 的值，移除列表中与参数 value 相等的元素。
     * <p>
     * count 的值可以是以下几种：
     * <p>
     * count > 0 : 从表头开始向表尾搜索，移除与 value 相等的元素，数量为 count 。
     * count < 0 : 从表尾开始向表头搜索，移除与 value 相等的元素，数量为 count 的绝对值。
     * count = 0 : 移除表中所有与 value 相等的值。
     *
     * @param key
     * @param count
     * @param value
     * @return
     */
    public Long lrem(String key, long count, Object value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }

    /**
     * 将列表 key 下标为 index 的元素的值设置为 value 。
     * <p>
     * 当 index 参数超出范围，或对一个空列表( key 不存在)进行 LSET 时，返回一个错误。
     * <p>
     * 关于列表下标的更多信息，请参考 LINDEX 命令。
     *
     * @param key
     * @param index
     * @param value
     */
    public void lset(String key, long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 对一个列表进行修剪(trim)，就是说，让列表只保留指定区间内的元素，不在指定区间之内的元素都将被删除。
     * <p>
     * 举个例子，执行命令 LTRIM list 0 2 ，表示只保留列表 list 的前三个元素，其余元素全部删除。
     * <p>
     * 下标(index)参数 start 和 stop 都以 0 为底，也就是说，以 0 表示列表的第一个元素，以 1 表示列表的第二个元素，以此类推。
     * <p>
     * 你也可以使用负数下标，以 -1 表示列表的最后一个元素， -2 表示列表的倒数第二个元素，以此类推。
     * <p>
     * 当 key 不是列表类型时，返回一个错误。
     *
     * @param key
     * @param start
     * @param end
     */
    public void ltrim(String key, long start, long end) {
        redisTemplate.opsForList().trim(key, start, end);
    }

    /**
     * 将值 value 插入到列表 key 的表尾，当且仅当 key 存在并且是一个列表。
     * <p>
     * 和 RPUSH 命令相反，当 key 不存在时， RPUSHX 命令什么也不做。
     *
     * @param key
     * @param value
     * @return
     */
    public Long rpushx(String key, Object value) {
        return redisTemplate.opsForList().rightPushIfPresent(key, value);
    }

    /**
     * 将一个值 value 插入到列表 key 的表头
     *
     * @param key
     * @param value
     * @return
     */
    public Long lpush(String key, Object value) {
        return redisTemplate.boundListOps(key).leftPush(value);
    }

    /**
     * 将多个值 value 插入到列表 key 的表头
     *
     * @param key
     * @param values
     * @return
     */
    public Long lpush(String key, Object... values) {
        return redisTemplate.boundListOps(key).leftPushAll(values);
    }

    /**
     * 将一个值 value 插入到列表 key 的表尾(最右边)。
     *
     * @param key
     * @param value
     * @return
     */
    public Long rpush(String key, Object value) {
        return redisTemplate.boundListOps(key).rightPush(value);
    }

    /**
     * 将多个值 value 插入到列表 key 的表尾(最右边)。
     *
     * @param key
     * @param values
     * @return
     */
    public Long rpush(String key, Object... values) {
        return redisTemplate.boundListOps(key).rightPushAll(values);
    }

    /**
     * 移除并返回列表 key 的头元素。
     *
     * @param key
     * @return
     */
    public Object lpop(String key) {
        return redisTemplate.boundListOps(key).leftPop();
    }

    /**
     * 移除并返回列表 key 的尾元素。
     *
     * @param key
     * @return
     */
    public Object rpop(String key) {
        return redisTemplate.boundListOps(key).rightPop();
    }

    /**
     * 返回列表 key 中指定区间内的元素，区间以偏移量 start 和 stop 指定。
     * <p>
     * 下标(index)参数 start 和 stop 都以 0 为底，也就是说，以 0 表示列表的第一个元素，以 1 表示列表的第二个元素，以此类推。
     * <p>
     * 你也可以使用负数下标，以 -1 表示列表的最后一个元素， -2 表示列表的倒数第二个元素，以此类推。
     * <p>
     * 假如你有一个包含一百个元素的列表，对该列表执行 LRANGE list 0 10 ，结果是一个包含11个元素的列表，这表明 stop 下标也在 LRANGE 命令的取值范围之内(闭区间)
     * <p>
     * 超出范围的下标值不会引起错误。
     * <p>
     * 如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大，那么 LRANGE 返回一个空列表。
     * <p>
     * 如果 stop 下标比 end 下标还要大，Redis将 stop 的值设置为 end 。
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<Object> lrange(String key, long start, long end) {
        return redisTemplate.boundListOps(key).range(start, end);
    }

    /**
     * 命令 RPOPLPUSH 在一个原子时间内，执行以下两个动作：
     * <p>
     * 将列表 source 中的最后一个元素(尾元素)弹出，并返回给客户端。
     * 将 source 弹出的元素插入到列表 destination ，作为 destination 列表的的头元素。
     * <p>
     * 如果 source 不存在，值 nil 被返回，并且不执行其他动作。
     * <p>
     * 如果 source 和 destination 相同，则列表中的表尾元素被移动到表头，并返回该元素，可以把这种特殊情况视作列表的旋转(rotation)操作。
     *
     * @param sourceKey
     * @param destinationKey
     * @return
     */
    public Object rpoplpush(String sourceKey, String destinationKey) {
        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);
    }

    /**
     * 返回列表 key 的长度。
     * <p>
     * 如果 key 不存在，则 key 被解释为一个空列表，返回 0 。
     * <p>
     * 如果 key 不是列表类型，返回一个错误。
     *
     * @param key
     * @return
     */
    public Long llen(String key) {
        return redisTemplate.boundListOps(key).size();
    }
    /**  List（列表）操作 - End ---------------------------------------------------*/
    /** ============================ */
    /**  Key（键）操作 - Begin       */
    /** ============================ */
    /**
     * 批量删除对应的value
     *
     * @param keys
     */
    public void remove(final String... keys) {
        redisTemplate.delete(Arrays.asList(keys));
    }

    /**
     * 批量删除对应的value
     *
     * @param keys
     */
    public void remove(final List keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 删除对应的value
     *
     * @param key
     */
    public void remove(final String key) {
        if (hasKey(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 批量删除key
     *
     * @param pattern
     */
    public void removePattern(final String pattern) {
        Set<String> keys = redisTemplate.keys(pattern);
        if (keys.size() > 0) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * 判断缓存中是否有对应的value
     *
     * @param key
     * @return
     */
    public boolean hasKey(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 设置过期时间
     *
     * @param key
     * @param timeout
     * @param unit
     * @return
     */
    public boolean expire(final String key, final long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 设置在某一时间点过期
     *
     * @param key
     * @param date
     * @return
     */
    public boolean expireAt(final String key, Date date) {
        return redisTemplate.expireAt(key, date);
    }

    /**
     * 重命名key
     *
     * @param oldKey
     * @param newKey
     * @return
     */
    public boolean rename(String oldKey, String newKey) {
        return redisTemplate.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 得到过期时间，秒为单位
     *
     * @param key
     * @return
     */
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key);

    }

    /**
     * 获取指定单位过期时间
     *
     * @param key      redis键值
     * @param timeUnit 时间单位
     * @return
     */
    public Long getExpire(String key, final TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }
    /**  Key（键）操作 - End -----------------------------------------------------*/

    /**
     * 递减操作
     *
     * @param key
     * @return
     */
    public Long decr(String key) {
        return incrBy(key, -1);
    }

    /**
     * 递增操作
     *
     * @param key
     * @return
     */
    public Long incr(String key) {
        return incrBy(key, 1);
    }

    /**
     * 递减操作
     *
     * @param key
     * @param delta
     * @return
     */
    public Long decrBy(String key, long delta) {
        return incrBy(key, (-1) * delta);
    }

    public double decrBy(String key, double delta) {
        return incrBy(key, (-1) * delta);
    }

    public Long incrBy(String key, long delta) {
        if (!hasKey(key)) {
            set(key, delta);
            return delta;
        } else {
            Object value = get(key);
            long ret = Long.parseLong(value.toString()) + delta;
            set(key, ret);
            return ret;
        }
    }

    public Double incrBy(String key, double delta) {
        if (!hasKey(key)) {
            set(key, delta);
            return delta;
        } else {
            Object value = get(key);
            double ret = Double.parseDouble(value.toString()) + delta;
            set(key, ret);
            return ret;
        }
    }

}